package com.mjk.common.core.jdbc.datasource;

import com.google.common.base.Preconditions;

import com.mjk.common.base.error.BizException;
import com.mjk.common.core.jdbc.annotation.ArcSharding;
import com.mjk.common.core.jdbc.service.plus.provider.BeeDataSourceType;
import com.mjk.common.core.jdbc.service.plus.provider.impl.wrap.mongodb.MongoDataSource;
import com.mjk.common.core.jdbc.sphere.CapacityAlgorithmUtil;
import com.mjk.common.core.jdbc.util.DataSourceUtil;
import com.mjk.common.util.ObjectUtils;
import com.mjk.common.util.PropertyUtil;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import lombok.Getter;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;
import java.util.Map.Entry;


/**
 * @author zcy 2018年10月16日
 */
@Configuration
@EnableConfigurationProperties({ DataSourceProperties.class })
@ConditionalOnProperty(name = "sharding.jdbc.type", havingValue = "sharding")
public class ShardingDataSouceAutoConf implements IDataSource {
	
	private ApplicationContext context;
	final
	DataSourceProperties commonProperties;
	
	@Getter
	private final Map<String, DataSource> dataSourceMap = new LinkedHashMap<>();
	
	@Getter
	private final Map<String, String> driverMap = new LinkedHashMap<>();
	
	private final Map<String, JdbcTemplate> jdbcTemplateMap = new LinkedHashMap<>();
	
	@Getter
	private   String defaultName = "";

	public ShardingDataSouceAutoConf(DataSourceProperties commonProperties) {
		this.commonProperties = commonProperties;
	}

	public void setDefaultName(String defaultName) {
		this.defaultName = defaultName;
	}
	@Bean(name="minidaoDataSource")
	public DataSource dataSource1() {
		return getJimuDataSource(dataSourceMap);
	}
	@Bean
	@Primary
	public DynamicDataSource dataSource() throws SQLException{
		Map<Object, Object> targetDataSources = new HashMap<>();
		String defaultDataSource = "";
		Properties properties = new Properties();
		Map<String,Object> map = context.getBeansWithAnnotation(ArcSharding.class);
		Map<String,ShardingRuleConfiguration> rulsMap = new HashMap<>();
		for(Entry<String,Object> entry : map.entrySet()) {
			ArcSharding sharding = entry.getValue().getClass().getAnnotation(ArcSharding.class);
			if (null==sharding||ObjectUtils.isEmpty(sharding.key())) {
				continue;
			}
			if(ObjectUtils.isEmpty(sharding.dataSource())){
				rulsMap.put(this.defaultName, CapacityAlgorithmUtil.getRuleConfig(sharding,this.defaultName));
			}else {
				rulsMap.put(sharding.dataSource(), CapacityAlgorithmUtil.getRuleConfig(sharding,this.defaultName));
			}
		}
		for(Entry<String,DataSource> entry : dataSourceMap.entrySet()){
			Map<String, DataSource> temDataSource = new HashMap<>();
			temDataSource.put(entry.getKey(),entry.getValue());	
			JdbcTemplate jdbcTemplate = new JdbcTemplate();
			if(rulsMap.containsKey(entry.getKey())) {
				DataSource dataSource = ShardingDataSourceFactory.createDataSource(temDataSource, rulsMap.get(entry.getKey()),properties);
				targetDataSources.put(entry.getKey(),dataSource);
				jdbcTemplate.setDataSource(dataSource);
			}else {
				targetDataSources.put(entry.getKey(), entry.getValue());
				jdbcTemplate.setDataSource(entry.getValue());
			}
			jdbcTemplateMap.put(entry.getKey(), jdbcTemplate);
			defaultDataSource = entry.getKey();
		}

		DynamicDataSource dataSource = new DynamicDataSource();
		dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
		dataSource.setDefaultTargetDataSource(targetDataSources.get(defaultDataSource));
		return dataSource;
	}

	@Override
	public final void setEnvironment(final Environment environment) {
		setDataSourceMap(environment);
	}

	@SuppressWarnings("unchecked")
	private void setDataSourceMap(final Environment environment) {
		String prefix = "sharding.jdbc.datasource.";
		String distrubTranscation = environment.getProperty("seata.enabled");
		String dataSources = environment.getProperty(prefix + "names");
		if (dataSources == null) {
			return;
		}
		for (String each : dataSources.split(",")) {
			try {
				Map<String, Object> dataSourceProps = PropertyUtil.handle(environment, prefix + each, Map.class);
				Preconditions.checkState(!dataSourceProps.isEmpty(), "Wrong datasource properties!");
				//判断是否是mongodb
				String type = dataSourceProps.get("type").toString();
				Preconditions.checkState(!ObjectUtils.isEmpty(type), "Wrong properties type!");
				if(BeeDataSourceType.MONGODB_WRAP.getDriver().equals(type)) {
					ConnectionString connString = new ConnectionString(dataSourceProps.get("jdbcUrl").toString());
					MongoClientSettings settings = MongoClientSettings.builder().applyConnectionString(connString)
							.retryWrites(true).build();
					MongoClient client = MongoClients.create(settings);
					dataSourceMap.put(each, new MongoDataSource(client));
					driverMap.put(each, type);
				}else {
					DataSource dataSource = DataSourceUtil.getDataSource(dataSourceProps.get("type").toString(),dataSourceProps);
					driverMap.put(each, dataSourceProps.get("driver-class-name").toString());	
					if("true".equals(distrubTranscation)) {
						dataSource = new DataSourceProxy(dataSource);
					}
                    //initJdbcMapping(dataSourceProps.get("driver-class-name").toString(),dataSource);
					dataSourceMap.put(each, dataSource);
				}
			} catch (final ReflectiveOperationException ex) {
				throw new BizException("Can't find datasource type!", ex);
			}
		}
		this.defaultName = environment.getProperty("sharding.jdbc.datasource.defaultname");
	}
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		context = applicationContext;
		
	}
	@Override
	public JdbcTemplate getJdbcTemplate(ArcSharding arcSharding) {
        if (null == arcSharding) {
            return this.jdbcTemplateMap.get(this.defaultName);
        }
		String dateSource = ObjectUtils.isNotEmpty(arcSharding.dataSource())?arcSharding.dataSource():this.defaultName;
		return this.jdbcTemplateMap.get(dateSource);
	}
	@Override
	public DataSource getDataSource(ArcSharding arcSharding) {
		if(null == arcSharding) {
			return  this.dataSourceMap.get(this.defaultName);
		}
		String dateSource = ObjectUtils.isNotEmpty(arcSharding.dataSource())?arcSharding.dataSource():this.defaultName;
		return this.dataSourceMap.get(dateSource);
	}
	@Override
	public Map<String, DataSource> getAllDataSource() {
		return this.dataSourceMap;
	}
	@Override
	public List<JdbcTemplate> getAllJdbcTemplate() {
		List<JdbcTemplate> result = new ArrayList<>();
		for(Entry<String, JdbcTemplate> entry : jdbcTemplateMap.entrySet()) {
			result.add(entry.getValue());
		}
		return result;
	}
}